/*
 * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform".
 *
 * (c) Matthias L. Jugel, Marcus Meißner 1996-2005. All Rights Reserved.
 *
 * Please visit http://javatelnet.org/ for updates and contact.
 *
 * --LICENSE NOTICE--
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * --LICENSE NOTICE--
 *
 */
package de.mud.jta;

import de.mud.jta.event.AppletRequest;
import de.mud.jta.event.FocusStatusListener;
import de.mud.jta.event.OnlineStatusListener;
import de.mud.jta.event.ReturnFocusRequest;
import de.mud.jta.event.SocketRequest;
import de.mud.jta.event.SoundListener;
import de.mud.jta.plugin.hips.HipsMessageProcessor;
import de.mud.jta.plugin.hips.HipsMessageListener;

import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Component;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.MenuShortcut;
import java.awt.PrintJob;
import java.awt.datatransfer.Clipboard;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;

import netscape.javascript.JSObject;

/**
 * <B>JTA - Telnet/SSH for the JAVA(tm) platform: Applet</B><P>
 * This is the implementation of whole set of applications. It's modular
 * structure allows to configure the software to act either as a sophisticated
 * terminal emulation and/or, adding the network backend, as telnet
 * implementation. Additional modules provide features like scripting or an
 * improved graphical user interface.<P>
 * This software is written entirely in Java<SUP>tm</SUP>.<P>
 * This is the <I>Applet</I> implementation for the software. It initializes
 * the system and adds all needed components, such as the telnet backend and
 * the terminal front end.
 * <P>
 * <B>Maintainer:</B> Matthias L. Jugel
 *
 * @version $Id: Applet.java 499 2005-09-29 08:24:54Z leo $
 * @author Matthias L. Jugel, Marcus Meissner
 */
public class Applet extends JApplet {

  private final static int debug = 0;

  private String frameTitle = null;
  private RootPaneContainer appletFrame;

  /** holds the defaults */
  private Properties options = new Properties();

  /** hold the common part of the jta */
  private Common pluginLoader;

  /** hold the host and port for our connection */
  private String host, port;

  /** disconnect on leave, this is to force applets to break the connection */
  private boolean disconnect = true;
  /** connect on startup, this is to force applets to connect on detach */
  private boolean connect = false;
  /** close the window (if it exists) after the connection is lost */
  private boolean disconnectCloseWindow = true;

  private Plugin focussedPlugin;
  private Clipboard clipboard;
  private boolean online = false;

  /**
   * Read all parameters from the applet configuration and
   * do initializations for the plugins and the applet.
   */
  public void init() {
      try {
          UIManager.setLookAndFeel( new com.nilo.plaf.nimrod.NimRODLookAndFeel());
      } catch (UnsupportedLookAndFeelException e) {
          e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
      }
      System.out.println("Starting applet " + toString());
      new Exception().printStackTrace();
    if (debug > 0) System.err.println("Applet: init()");

    host = getParameter("proxyHost");
    port = getParameter("proxyPort");
    if (pluginLoader == null) {
      try {
        options.load(Applet.class
                     .getResourceAsStream("/de/mud/jta/default.conf"));
      } catch (Exception e) {
        try {
          URL url = new URL(getCodeBase() + "default.conf");
          options.load(url.openStream());
        } catch (Exception e1) {
          System.err.println("jta: cannot load default.conf");
          System.err.println("jta: try extracting it from the jar file");
          System.err.println("jta: expected file here: "
                             + getCodeBase() + "default.conf");
        }
      }

      String value;

      // try to load the local configuration and merge it with the defaults
      if ((value = getParameter("config")) != null) {
        Properties appletParams = new Properties();
        URL url = null;
        try {
          url = new URL(value);
        } catch (Exception e) {
          try {
            url = new URL(getCodeBase() + value);
          } catch (Exception ce) {
            System.err.println("jta: could not find config file: " + ce);
          }
        }

        if (url != null) {
          try {
            appletParams.load(Applet.class.getResourceAsStream("/de/mud/jta/" + value));
            Enumeration ape = appletParams.keys();
            while (ape.hasMoreElements()) {
              String key = (String) ape.nextElement();
              options.put(key, appletParams.getProperty(key));
            }
          } catch (Exception e) {
            try {
              appletParams.load(url.openStream());
              Enumeration ape = appletParams.keys();
              while (ape.hasMoreElements()) {
                String key = (String) ape.nextElement();
                options.put(key, appletParams.getProperty(key));
              }
            } catch (Exception e2) {
              System.err.println("jta: could not load config file: " + e2);
            }
          }
        }
      }

      // see if there are parameters in the html to override properties
      parameterOverride(options);

      // configure the application and load all plugins
      pluginLoader = new Common(options);

      // set the host to our code base, no other hosts are allowed anyway
      //host = options.getProperty("Socket.host");
      //if (host == null)
      //  host = getCodeBase().getHost();
      //port = options.getProperty("Socket.port");
      //if (port == null)
      //  port = "23";

      if ((new Boolean(options.getProperty("Applet.connect"))
              .booleanValue()))
        connect = true;
      if (!(new Boolean(options.getProperty("Applet.disconnect"))
              .booleanValue()))
        disconnect = false;

      if (!(new Boolean(options.getProperty("Applet.disconnect.closeWindow"))
              .booleanValue()))
        disconnectCloseWindow = false;

      frameTitle = options.getProperty("Applet.detach.title");

      if ((new Boolean(options.getProperty("Applet.detach"))).booleanValue()) {
        if (frameTitle == null) {
          appletFrame = (RootPaneContainer)new JFrame("jta: " + host + (port.equals("23")?"":" " + port));
        } else {
          appletFrame = (RootPaneContainer)new JFrame(frameTitle);
        }
      } else {
        appletFrame = (RootPaneContainer)this;
      }
      appletFrame.getContentPane().setLayout(new BorderLayout());

      Map componentList = pluginLoader.getComponents();
      Iterator names = componentList.keySet().iterator();
      while (names.hasNext()) {
        String name = (String) names.next();
        Component c = (Component) componentList.get(name);
        if ((value = options.getProperty("layout." + name)) != null) {
          appletFrame.getContentPane().add(value, c);
        } else {
          System.err.println("jta: no layout property set for '" + name + "'");
          System.err.println("jta: ignoring '" + name + "'");
        }
      }

      pluginLoader.registerPluginListener(new SoundListener() {
        public void playSound(URL audioClip) {
          Applet.this.getAudioClip(audioClip).play();
        }
      });

      pluginLoader.broadcast(new AppletRequest(this));
      if (appletFrame != this) {
        final String startText = options.getProperty("Applet.detach.startText");
        final String stopText = options.getProperty("Applet.detach.stopText");
        final Button close = new Button();

        // this works for Netscape only!
        Vector privileges =
                Common.split(options.getProperty("Applet.Netscape.privilege"), ',');
        Class privilegeManager = null;
        Method enable = null;
        try {
          privilegeManager =
                  Class.forName("netscape.security.PrivilegeManager");
          enable = privilegeManager
                  .getMethod("enablePrivilege", new Class[]{String.class});
        } catch (Exception e) {
          System.err.println("Applet: This is not Netscape ...");
        }

        if (privilegeManager != null && enable != null && privileges != null)
          for (int i = 0; i < privileges.size(); i++)
            try {
              enable.invoke(privilegeManager,
                            new Object[]{privileges.elementAt(i)});
              System.out.println("Applet: access for '" +
                                 privileges.elementAt(i) + "' allowed");

            } catch (Exception e) {
              System.err.println("Applet: access for '" +
                                 privileges.elementAt(i) + "' denied");
            }

        // set up the clipboard
        try {
          clipboard = appletFrame.getContentPane().getToolkit().getSystemClipboard();
          System.err.println("Applet: acquired system clipboard: " + clipboard);
        } catch (Exception e) {
          System.err.println("Applet: system clipboard access denied: " +
                             ((e instanceof InvocationTargetException) ?
                              ((InvocationTargetException) e).getTargetException() : e));
          // e.printStackTrace();
        } finally {
          if (clipboard == null) {
            System.err.println("Applet: copy & paste only within the JTA");
            clipboard = new Clipboard("de.mud.jta.Main");
          }
        }

        if ((new Boolean(options.getProperty("Applet.detach.immediately"))
                .booleanValue())) {
          if ((new Boolean(options.getProperty("Applet.detach.fullscreen"))
                  .booleanValue()))
            ((JFrame) appletFrame)
                    .setSize(appletFrame.getContentPane().getToolkit().getScreenSize());
          else
            ((JFrame) appletFrame).pack();

          ((JFrame) appletFrame).show();
          pluginLoader.broadcast(new SocketRequest(host, Integer.parseInt(port)));
          pluginLoader.broadcast(new ReturnFocusRequest());
          close.setLabel(startText != null ? stopText : "Disconnect");
        } else
          close.setLabel(startText != null ? startText : "Connect");

        close.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent evt) {
            if (((JFrame) appletFrame).isVisible()) {
              pluginLoader.broadcast(new SocketRequest());
              ((JFrame) appletFrame).setVisible(false);
              close.setLabel(startText != null ? startText : "Connect");
            } else {
              if (frameTitle == null)
                ((JFrame) appletFrame)
                        .setTitle("jta: " + host + (port.equals("23")?"":" " + port));
              if ((new Boolean(options.getProperty("Applet.detach.fullscreen"))
                      .booleanValue()))
                ((JFrame) appletFrame)
                        .setSize(appletFrame.getContentPane().getToolkit().getScreenSize());
              else
                ((JFrame) appletFrame).pack();
              ((JFrame) appletFrame).show();
              if (port == null || port.length() <= 0)
		port = "23";
              getAppletContext().showStatus("Trying " + host + " " + port + " ...");
              pluginLoader.broadcast(new SocketRequest(host,
                                                       Integer.parseInt(port)));
              pluginLoader.broadcast(new ReturnFocusRequest());
              close.setLabel(stopText != null ? stopText : "Disconnect");
            }
          }
        });

        getContentPane().setLayout(new BorderLayout());
        getContentPane().add("Center", close);

        // add a menu bar
        MenuBar mb = new MenuBar();
        Menu file = new Menu("File");
        file.setShortcut(new MenuShortcut(KeyEvent.VK_F, true));
        MenuItem tmp;
        file.add(tmp = new MenuItem("Connect"));
        tmp.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent evt) {
            pluginLoader.broadcast(new SocketRequest(host,
                                                     Integer.parseInt(port)));
          }
        });
        file.add(tmp = new MenuItem("Disconnect"));
        tmp.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent evt) {
            pluginLoader.broadcast(new SocketRequest());
          }
        });
        file.add(new MenuItem("-"));
        file.add(tmp = new MenuItem("Print"));
        tmp.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent evt) {
            if (pluginLoader.getComponents().get("Terminal") != null) {
              PrintJob printJob =
                      appletFrame.getContentPane().getToolkit()
                      .getPrintJob((JFrame) appletFrame, "JTA Terminal", null);
              ((Component) pluginLoader.getComponents().get("Terminal"))
                      .print(printJob.getGraphics());
              printJob.end();
            }
          }
        });
        file.add(new MenuItem("-"));
        file.add(tmp = new MenuItem("Exit"));
        tmp.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent evt) {
            ((JFrame) appletFrame).setVisible(false);
            pluginLoader.broadcast(new SocketRequest());
            close.setLabel(startText != null ? startText : "Connect");
          }
        });
        mb.add(file);

        Menu edit = new Menu("Edit");
        edit.setShortcut(new MenuShortcut(KeyEvent.VK_E, true));
        edit.add(tmp = new MenuItem("Copy"));
        tmp.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent evt) {
            if (debug > 2)
              System.err.println("Applet: copy: " + focussedPlugin);
            if (focussedPlugin instanceof VisualTransferPlugin)
              ((VisualTransferPlugin) focussedPlugin).copy(clipboard);
          }
        });
        edit.add(tmp = new MenuItem("Paste"));
        tmp.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent evt) {
            if (debug > 2)
              System.err.println("Applet: paste: " + focussedPlugin);
            if (focussedPlugin instanceof VisualTransferPlugin)
              ((VisualTransferPlugin) focussedPlugin).paste(clipboard);
          }
        });
        mb.add(edit);

        Map menuList = pluginLoader.getMenus();
        names = menuList.keySet().iterator();
        while (names.hasNext()) {
          String name = (String) names.next();
	  Object o = menuList.get(name);
          if (o instanceof Menu) mb.add((Menu) o);
        }

        Menu help = new Menu("Help");
        help.setShortcut(new MenuShortcut(KeyEvent.VK_HELP, true));
        help.add(tmp = new MenuItem("General"));
        tmp.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            Help.show(appletFrame.getContentPane(), options.getProperty("Help.url"));
          }
        });
        mb.setHelpMenu(help);

        // only add the menubar if the property is true
        if ((new Boolean(options.getProperty("Applet.detach.menuBar"))
                .booleanValue()))
          ((JFrame) appletFrame).setMenuBar(mb);

        // add window closing event handler
        try {
          ((JFrame) appletFrame).addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent evt) {
              pluginLoader.broadcast(new SocketRequest());
              ((JFrame) appletFrame).setVisible(false);
              close.setLabel(startText != null ? startText : "Connect");
            }
          });
        } catch (Exception e) {
          System.err.println("Applet: could not set up Window event listener");
          System.err.println("Applet: you will not be able to close it");
        }

        pluginLoader.registerPluginListener(new OnlineStatusListener() {
          public void online() {
            if (debug > 0) System.err.println("Terminal: online");
            online = true;
            if (((JFrame) appletFrame).isVisible() == false)
              ((JFrame) appletFrame).setVisible(true);
          }

          public void offline() {
            if (debug > 0) System.err.println("Terminal: offline");
            online = false;
            if (disconnectCloseWindow) {
              ((JFrame) appletFrame).setVisible(false);
              close.setLabel(startText != null ? startText : "Connect");
            }
          }
        });

        // register a focus status listener, so we know when a plugin got focus
        pluginLoader.registerPluginListener(new FocusStatusListener() {
          public void pluginGainedFocus(Plugin plugin) {
            if (Applet.debug > 0)
              System.err.println("Applet: " + plugin + " got focus");
            focussedPlugin = plugin;
          }

          public void pluginLostFocus(Plugin plugin) {
            // we ignore the lost focus
            if (Applet.debug > 0)
              System.err.println("Applet: " + plugin + " lost focus");
          }
        });

      } else
      // if we have no external frame use this online status listener
        pluginLoader.registerPluginListener(new OnlineStatusListener() {
          public void online() {
            if (debug > 0) System.err.println("Terminal: online");
            online = true;
          }

          public void offline() {
            if (debug > 0) System.err.println("Terminal: offline");
            online = false;
          }
        });


    }
      System.out.println("====Finished init");
  }

  /**
   * Start the applet. Connect to the remote host.
   */
  public void start() {
    if (!online && (appletFrame == this || connect)) {
      if (debug > 0) System.err.println("start(" + host + ", " + port + ")");
      getAppletContext().showStatus("Trying " + host + " " + port + " ...");
      pluginLoader.broadcast(new SocketRequest(host, Integer.parseInt(port)));
      pluginLoader.broadcast(new ReturnFocusRequest());
    }
  }

    public void destroy() {
           System.out.println("Destroying applet");
            new Exception().printStackTrace();
        }


  /**
   * Stop the applet and disconnect.
   */
  public void stop() {
    if (online && disconnect) {
      if (debug > 0) System.err.println("stop()");
      pluginLoader.broadcast(new SocketRequest());
    }
      System.out.println("Stopping applet");
  }

  public void addHipsMessageListener(final String ns, final String methodName) {
      System.out.println("Registering method "+methodName+" for namespace "+ns);
      HipsMessageProcessor.getInstance().addListener(ns, new HipsMessageListener() {
          public void handle(String name, String message) {
              System.out.println("Handling message "+message+" for applet: "+Applet.this.toString());
              JSObject win = JSObject.getWindow(Applet.this);
              win.call(methodName, new Object[]{name, message});
          }
      });
  }

  public int getHipsListenerCount() {
      return HipsMessageProcessor.getInstance().getListenerCount();
  }

  /**
   * Override any properties that are found in the configuration files
   * with possible values found as applet parameters.
   * @param options the loaded configuration file properties
   */
  private void parameterOverride(Properties options) {
    Enumeration e = options.keys();
    while (e.hasMoreElements()) {
      String key = (String) e.nextElement(), value = getParameter(key);
      if (value != null) {
        System.out.println("Applet: overriding value of " + key + " with " + value);
        // options.setProperty(key, value);
        options.put(key, value);
      }
    }
  }
}
